pub fn is_leap_year(year: u32) -> bool {
    if year % 4 == 0 && year % 100 != 0 || year % 400 == 0 {
        true
    } else {
        false
    }
}

pub fn days_of_month(year: u32, month: u32) -> u32 {
    match month {
        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
        4 | 6 | 9 | 11 => 30,
        2 => {
            if is_leap_year(year) {
                29
            } else {
                28
            }
        }
        _ => 0,
    }
}

// 1-7: Monday-Sunday
pub fn day_of_week(year: u32, month: u32, day: u32) -> u32 {
    let oracle = days_from_oracle(year, month, day);
    oracle % 7 + 1
}

fn first_week_oracle(year: u32) -> u32 {
    let day_of_week = day_of_week(year, 1, 1);
    // ISO 8601定义包含当年第一个星期四的那一周是第一个星期
    let first_thusday_oracle = days_from_oracle(
        year,
        1,
        if day_of_week <= 4 {
            5 - day_of_week
        } else {
            12 - day_of_week
        },
    );
    first_thusday_oracle - 3
}

pub fn week_of_year(year: u32, month: u32, day: u32) -> u32 {
    let oracle = days_from_oracle(year, month, day);
    let first_week_oracle_ = first_week_oracle(year);
    if oracle < first_week_oracle_ {
        // in last year
        oracle / 7 - first_week_oracle(year - 1) / 7 + 1
    } else if oracle >= first_week_oracle(year + 1) {
        // in next year
        1
    } else {
        // in this year
        oracle / 7 - first_week_oracle_ / 7 + 1
    }
}

pub fn day_of_year(year: u32, month: u32, day: u32) -> u32 {
    let mut days = 0;
    for m in 1..month {
        days += days_of_month(year, m);
    }
    days + day
}

pub fn left_days_of_year(year: u32, month: u32, day: u32) -> u32 {
    let days = day_of_year(year, month, day);
    if is_leap_year(year) {
        366 - days
    } else {
        365 - days
    }
}

static MIN_YEAR: u32 = 1900;

/// 数据结构如下，共使用17位数据
/// 第17位：表示闰月天数，0表示29天   1表示30天
/// 第16位-第5位（共12位）表示12个月，其中第16位表示第一月，如果该月为30天则为1，29天为0
/// 第4位-第1位（共4位）表示闰月是哪个月，如果当年没有闰月，则置0
static LUNAR_DATE_ARRAY: [u32; 151] = [
    0x04BD8, 0x04AE0, 0x0A570, 0x054D5, 0x0D260, 0x0D950, 0x16554, 0x056A0, 0x09AD0, 0x055D2,
    0x04AE0, 0x0A5B6, 0x0A4D0, 0x0D250, 0x1D255, 0x0B540, 0x0D6A0, 0x0ADA2, 0x095B0, 0x14977,
    0x04970, 0x0A4B0, 0x0B4B5, 0x06A50, 0x06D40, 0x1AB54, 0x02B60, 0x09570, 0x052F2, 0x04970,
    0x06566, 0x0D4A0, 0x0EA50, 0x06E95, 0x05AD0, 0x02B60, 0x186E3, 0x092E0, 0x1C8D7, 0x0C950,
    0x0D4A0, 0x1D8A6, 0x0B550, 0x056A0, 0x1A5B4, 0x025D0, 0x092D0, 0x0D2B2, 0x0A950, 0x0B557,
    0x06CA0, 0x0B550, 0x15355, 0x04DA0, 0x0A5B0, 0x14573, 0x052B0, 0x0A9A8, 0x0E950, 0x06AA0,
    0x0AEA6, 0x0AB50, 0x04B60, 0x0AAE4, 0x0A570, 0x05260, 0x0F263, 0x0D950, 0x05B57, 0x056A0,
    0x096D0, 0x04DD5, 0x04AD0, 0x0A4D0, 0x0D4D4, 0x0D250, 0x0D558, 0x0B540, 0x0B6A0, 0x195A6,
    0x095B0, 0x049B0, 0x0A974, 0x0A4B0, 0x0B27A, 0x06A50, 0x06D40, 0x0AF46, 0x0AB60, 0x09570,
    0x04AF5, 0x04970, 0x064B0, 0x074A3, 0x0EA50, 0x06B58, 0x055C0, 0x0AB60, 0x096D5, 0x092E0,
    0x0C960, 0x0D954, 0x0D4A0, 0x0DA50, 0x07552, 0x056A0, 0x0ABB7, 0x025D0, 0x092D0, 0x0CAB5,
    0x0A950, 0x0B4A0, 0x0BAA4, 0x0AD50, 0x055D9, 0x04BA0, 0x0A5B0, 0x15176, 0x052B0, 0x0A930,
    0x07954, 0x06AA0, 0x0AD50, 0x05B52, 0x04B60, 0x0A6E6, 0x0A4E0, 0x0D260, 0x0EA65, 0x0D530,
    0x05AA0, 0x076A3, 0x096D0, 0x04BD7, 0x04AD0, 0x0A4D0, 0x1D0B6, 0x0D250, 0x0D520, 0x0DD45,
    0x0B5A0, 0x056D0, 0x055B2, 0x049B0, 0x0A577, 0x0A4B0, 0x0AA50, 0x1B255, 0x06D20, 0x0ADA0,
    0x14B63,
];

pub fn bit_test(num: u32, bit: u32) -> bool {
    (num & (1 << bit)) != 0
}

type ChineseMonth = (u32, bool);

pub fn get_chinese_leap_month(year: u32) -> Option<u32> {
    let m = LUNAR_DATE_ARRAY[(year - MIN_YEAR) as usize] & 0xF;
    if m == 0 {
        None
    } else {
        Some(m)
    }
}

pub fn days_of_chinese_leap_month(year: u32) -> u32 {
    bit_test(LUNAR_DATE_ARRAY[(year - MIN_YEAR) as usize], 16) as u32 + 29
}

pub fn days_of_chinese_month(year: u32, cmonth: ChineseMonth) -> u32 {
    if cmonth.1 {
        days_of_chinese_leap_month(year)
    } else {
        bit_test(LUNAR_DATE_ARRAY[(year - MIN_YEAR) as usize], 16 - cmonth.0) as u32 + 29
    }
}

pub fn days_of_year_chinese(year: u32) -> u32 {
    let leap = get_chinese_leap_month(year);
    let mut days = 0;
    for m in 1..13 {
        days += days_of_chinese_month(year, (m, false));
        if leap == Some(m) {
            days += days_of_chinese_leap_month(year);
        }
    }
    days
}

pub fn day_of_year_chinese(year: u32, month: ChineseMonth, day: u32) -> u32 {
    let mut days = 0;
    let leap = get_chinese_leap_month(year);
    for m in 1..month.0 {
        days += days_of_chinese_month(year, (m, false));
        if leap == Some(m) {
            days += days_of_chinese_leap_month(year);
        }
    }
    if month.1 {
        days += days_of_chinese_leap_month(year);
    }
    days + day
}

// Get elapsed days from oracle 1900-01-01 (lunar 1899-12-01)
pub fn days_from_oracle(year: u32, month: u32, day: u32) -> u32 {
    let mut days: u32 = 0;
    for y in MIN_YEAR..year {
        days += 365;
        if is_leap_year(y) {
            days += 1;
        }
    }
    days + day_of_year(year, month, day) - day_of_year(1900, 1, 1)
}

// Get elapsed days from oracle 1900-01-01 (lunar 1899-12-01)
pub fn days_from_oracle_chinese(year: u32, cmonth: ChineseMonth, day: u32) -> u32 {
    // Get elapsed days from 1900-01-31 (lunar 1900-01-01)
    let mut days: u32 = 0;
    for y in MIN_YEAR..year {
        days += days_of_year_chinese(y);
    }
    // 1900-01-01 (lunar 1899-12-01) to 1900-01-31 (lunar 1900-01-01) is 30 days
    days + day_of_year_chinese(year, cmonth, day) - day_of_year_chinese(1900, (1, false), 1) + 30
}

pub fn oracle_to_date(days: u32) -> (u32, u32, u32) {
    let mut year = MIN_YEAR;
    let mut days = days + 1;
    while days > 365 {
        if is_leap_year(year) {
            if days > 366 {
                days -= 366;
            } else {
                break;
            }
        } else {
            days -= 365;
        }
        year += 1;
    }
    let mut month = 1;
    let mut day = days;
    while day > days_of_month(year, month) {
        day -= days_of_month(year, month);
        month += 1;
    }
    (year, month, day)
}

pub fn oracle_to_date_chinese(days: u32) -> (u32, ChineseMonth, u32) {
    let mut year = MIN_YEAR;
    let mut days = days + 1 - 30;
    while days > days_of_year_chinese(year) {
        days -= days_of_year_chinese(year);
        year += 1;
    }
    let mut cmonth = (1, false);
    let mut day = days;
    while day > days_of_chinese_month(year, cmonth) {
        day -= days_of_chinese_month(year, cmonth);

        if get_chinese_leap_month(year) == Some(cmonth.0) {
            cmonth.1 = true;
        } else if cmonth.1 {
            cmonth.1 = false;
            cmonth.0 += 1;
        } else {
            cmonth.0 += 1;
        }
    }
    (year, cmonth, day)
}

pub fn time_info(time: &str) -> String {
    let time = time.trim();
    let time: Vec<&str> = time.split('-').collect();
    let year = time[0].parse::<u32>().unwrap();
    let month = time[1].parse::<u32>().unwrap();
    let day = time[2].parse::<u32>().unwrap();

    let week = week_of_year(year, month, day);
    let left_days = left_days_of_year(year, month, day);

    let oracle = days_from_oracle(year, month, day);
    let (lunar_year, lunar_month, lunar_day) = oracle_to_date_chinese(oracle);
    let chinese_new_year = if lunar_month == (1, false) && lunar_day == 1 {
        // chinese new year!
        oracle
    } else {
        days_from_oracle_chinese(lunar_year + 1, (1, false), 1)
    };

    println!(
        "time: {:?} (lunar {:?}), oracle: {:?}, answer: {},{},{}",
        time,
        oracle_to_date_chinese(oracle),
        oracle,
        week,
        left_days,
        chinese_new_year - oracle - 1
    );
    format!("{},{},{}", week, left_days, chinese_new_year - oracle - 1)
}
